package com.sdkj.controller.activiti;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import net.sf.json.JSONObject;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonObject;
import com.sdkj.controller.base.BaseController;
import com.sdkj.entity.Page;
import com.sdkj.util.Const;
import com.sdkj.util.PageData;

/**
 * 流程定义相关Controller
 * @author leiweiping
 * @version 2013-11-03
 */
@Controller
@RequestMapping(value = "actprocess")
public class ActProcessController extends BaseController {

	@Resource
	private RepositoryService repositoryService;
	
	@Resource
	private RuntimeService runtimeService;
	/**
	 * 流程定义列表
	 */
	@RequestMapping("/list")
	public ModelAndView list(Page page) {
		ModelAndView mv = this.getModelAndView();
		PageData pd = new PageData();
		//保存两个对象，一个是ProcessDefinition（流程定义），一个是Deployment（流程部署） 
		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery()
		    		.latestVersion().orderByProcessDefinitionKey().asc();
		 
		pd = this.getPageData();
		String category = pd.getString("category");
		page.setPd(pd);
	    if (StringUtils.isNotEmpty(category)){
	    	processDefinitionQuery.processDefinitionCategory(category);
		}
	    List<Object[]> varList = new ArrayList<Object[]>();
	    List<ProcessDefinition> processDefinitionList = processDefinitionQuery.list();
	    for (ProcessDefinition processDefinition : processDefinitionList) {
	      String deploymentId = processDefinition.getDeploymentId();
	      Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
	      varList.add(new Object[]{processDefinition, deployment});
	    }
	    mv.setViewName("activiti/process/process_list");
		mv.addObject("varList", varList);
		mv.addObject("category",category);
		mv.addObject("pd", pd);
		mv.addObject(Const.SESSION_QX,this.getHC());	//按钮权限
		return mv;
	}
	
	/**
	 * 运行中的实例列表
	 */
	@RequestMapping(value = "/running")
	public ModelAndView runningList(Page page) {
		ModelAndView mv = this.getModelAndView();
		PageData pd = new PageData();
		pd = this.getPageData();
		String procInsId = pd.getString("procInsId"); 
		String procDefKey = pd.getString("procDefKey");
	    ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();

	    if (StringUtils.isNotBlank(procInsId)){
		    processInstanceQuery.processInstanceId(procInsId);
	    }
	    
	    if (StringUtils.isNotBlank(procDefKey)){
		    processInstanceQuery.processDefinitionKey(procDefKey);
	    }
	    
	    List<ProcessInstance> varList = processInstanceQuery.list();
		
	    mv.setViewName("activiti/process/process_running_list");
		mv.addObject("varList", varList);
		mv.addObject("pd", pd);
		mv.addObject(Const.SESSION_QX,this.getHC());	//按钮权限
	    
		return mv;
	}

	/**
	 * 读取资源，通过部署ID
	 * @param processDefinitionId  流程定义ID
	 * @param processInstanceId 流程实例ID
	 * @param resourceType 资源类型(xml|image)
	 * @param response
	 * @throws Exception
	 */
	@RequestMapping(value = "/resourceRead")
	public void resourceRead(String procDefId, String proInsId, String resType, HttpServletResponse response) throws Exception {
		if (StringUtils.isBlank(procDefId)){
			ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(proInsId).singleResult();
			procDefId = processInstance.getProcessDefinitionId();
		}
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId).singleResult();
		
		String resourceName = "";
		if (resType.equals("image")) {
			resourceName = processDefinition.getDiagramResourceName();
		} else if (resType.equals("xml")) {
			resourceName = processDefinition.getResourceName();
		}
		
		InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
		byte[] b = new byte[1024];
		int len = -1;
		response.setCharacterEncoding("UTF-8");
		while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
			response.getOutputStream().write(b, 0, len);
		}
	}

	/**
	 * 部署流程
	 */
	@RequestMapping(value = "/deploy")
	public ModelAndView deploy() {
		logBefore(logger, "去部署流程模型页面");
		ModelAndView mv = this.getModelAndView();
		PageData pd = new PageData();
		pd = this.getPageData();
		try {
			mv.setViewName("activiti/model/model_edit");
			mv.addObject("msg", "save");
			mv.addObject("pd", pd);
		} catch (Exception e) {
			logger.error(e.toString(), e);
		}						
		return mv;
	}
	
	/**
	 * 跳转部署流程页面
	 */
	@RequestMapping(value = "/deployfile")
	public ModelAndView deployfile() {
		ModelAndView mv = this.getModelAndView();
		PageData pd = new PageData();
		pd = this.getPageData();
		try {
			mv.setViewName("activiti/process/process_deploy_list");
			mv.addObject("msg", "save");
			mv.addObject("pd", pd);
		} catch (Exception e) {
			logger.error(e.toString(), e);
		}						
		return mv;
	}
	
	/**
	 * 部署流程 - 保存
	 * @param file
	 * @return
	 */
	@RequestMapping(value = "/deploysave",produces = "text/plain; charset=UTF-8")
	public void deploysave( String exportDir, String category, MultipartFile file,HttpServletResponse response) {
		String fileName = file.getOriginalFilename();
		JSONObject message = new JSONObject();
		try {
			PrintWriter out = response.getWriter();
			response.setCharacterEncoding("utf-8");
			InputStream fileInputStream = file.getInputStream();
			Deployment deployment;
			String extension = FilenameUtils.getExtension(fileName);
			if (extension.equals("zip") || extension.equals("bar")) {
				ZipInputStream zip = new ZipInputStream(fileInputStream);
				deployment = repositoryService.createDeployment().addZipInputStream(zip).deploy();
			} else if (extension.equals("png")) {
				deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
			} else if (fileName.indexOf("bpmn20.xml") != -1) {
				deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).deploy();
			} else if (extension.equals("bpmn")) { // bpmn扩展名特殊处理，转换为bpmn20.xml
				String baseName = FilenameUtils.getBaseName(fileName); 
				deployment = repositoryService.createDeployment().addInputStream(baseName + ".bpmn20.xml", fileInputStream).deploy();
			} else {
				message.put("status", false);
				message.put("message", extension);
				out.write(message.toString());
				out.flush();
				out.close();
				return;
			}
			
			List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();

			// 设置流程分类
			for (ProcessDefinition processDefinition : list) {
				repositoryService.setProcessDefinitionCategory(processDefinition.getId(), category);
				message.put("status", true);
				message.put("message", processDefinition.getId());
			}
			
			if (list.size() == 0){
				message.put("status", false);
				message.put("message", "");
			}
			out.write(message.toString());
			out.flush();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			logger.info("部署失败！");
		}
		
	}
	
	/**
	 * 设置流程分类
	 */
	@RequestMapping(value = "updateCategory")
	public ModelAndView updateCategory() {
		ModelAndView mv = this.getModelAndView();
		PageData pd = new PageData();
		pd = this.getPageData();
		String procDefId = pd.getString("procDefId"); 
		String category = pd.getString("category");
		try {
			repositoryService.setProcessDefinitionCategory(procDefId, category);
			mv.setViewName("activiti/process/process_edit");
			mv.addObject("pd", pd);
		} catch (Exception e) {
			logger.error(e.toString(), e);
		}						
		return mv;
	}

	/**
	 * 挂起、激活流程实例
	 */
	@RequestMapping(value = "update/{state}")
	public void updateState(@PathVariable("state") String state, String procDefId,HttpServletResponse response) throws Exception{
		String message = "";
		if (state.equals("active")) {
			repositoryService.activateProcessDefinitionById(procDefId, true, null);
			message = "已激活ID为[" + procDefId + "]的流程定义。";
		} else if (state.equals("suspend")) {
			repositoryService.suspendProcessDefinitionById(procDefId, true, null);
			message = "已挂起ID为[" + procDefId + "]的流程定义。";
		}
		message = "无操作";
		try{
			PrintWriter out;
			response.setCharacterEncoding("utf-8");
			out = response.getWriter();
			out.write(message);
			out.flush();
			out.close();
		} catch(Exception e){
			logger.error(e.toString(), e);
		}
	}
	
	/**
	 * 将部署的流程转换为模型
	 * @param procDefId
	 * @param redirectAttributes
	 * @return
	 * @throws UnsupportedEncodingException
	 * @throws XMLStreamException
	 */
	@RequestMapping(value = "/toModel")
	public String convertToModel(String procDefId,RedirectAttributes redirectAttributes)  {
		Model modelData;
		String message = "";
		try {
			ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId).singleResult();
			InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
			processDefinition.getResourceName());
			XMLInputFactory xif = XMLInputFactory.newInstance();
			InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
			XMLStreamReader xtr = xif.createXMLStreamReader(in);
			BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

			BpmnJsonConverter converter = new BpmnJsonConverter();
			ObjectNode modelNode = converter.convertToJson(bpmnModel);
			modelData = repositoryService.newModel();
			modelData.setKey(processDefinition.getKey());
			modelData.setName(processDefinition.getResourceName());
			modelData.setCategory(processDefinition.getCategory());//.getDeploymentId());
			modelData.setDeploymentId(processDefinition.getDeploymentId());
			modelData.setVersion(Integer.parseInt(String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count()+1)));

			ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
			modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
			modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
			modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
			modelData.setMetaInfo(modelObjectNode.toString());

			repositoryService.saveModel(modelData);

			repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
			message = "转换模型成功，模型ID="+modelData.getId();
		} catch (Exception e) {
			e.printStackTrace();
			message = "转换模型失败";
		}
		redirectAttributes.addFlashAttribute("message", message);
		return "redirect:/actmodel/list.do";
	}
	
	/**
	 * 导出图片文件到硬盘
	 */
	@RequestMapping(value = "export/diagrams")
	@ResponseBody
	public List<String> exportDiagrams(@Value("#{APP_PROP['activiti.export.diagram.path']}") String exportDir) {
		List<String> files = new ArrayList<String>();
		List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
		
		for (ProcessDefinition processDefinition : list) {
			String diagramResourceName = processDefinition.getDiagramResourceName();
			String key = processDefinition.getKey();
			int version = processDefinition.getVersion();
			String diagramPath = "";

			InputStream resourceAsStream = repositoryService.getResourceAsStream(
					processDefinition.getDeploymentId(), diagramResourceName);

			try {
				byte[] b = new byte[resourceAsStream.available()];

				int len = -1;
				resourceAsStream.read(b, 0, b.length);

				// create file if not exist
				String diagramDir = exportDir + "/" + key + "/" + version;
				File diagramDirFile = new File(diagramDir);
				if (!diagramDirFile.exists()) {
					diagramDirFile.mkdirs();
				}
				diagramPath = diagramDir + "/" + diagramResourceName;
				File file = new File(diagramPath);
				// 文件存在退出
				if (file.exists()) {
					// 文件大小相同时直接返回否则重新创建文件(可能损坏)
					logger.debug("diagram exist, ignore... : {}");
					logger.debug(diagramPath);
					
					files.add(diagramPath);
				} else {
					file.createNewFile();
					logger.debug("export diagram to : {}");
					logger.debug(diagramPath);

					// wirte bytes to file
					FileUtils.writeByteArrayToFile(file, b, true);
					
					files.add(diagramPath);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return files;
	}

	/**
	 * 删除部署的流程，级联删除流程实例
	 * @param deploymentId 流程部署ID
	 */
	@RequestMapping(value = "/delete")
	public void delete(String deploymentId, HttpServletResponse response) {
		repositoryService.deleteDeployment(deploymentId, true);
		String message = "删除部署流程成功，ID="+deploymentId;
		try{
			PrintWriter out;
			response.setCharacterEncoding("utf-8");
			out = response.getWriter();
			out.write(message);
			out.flush();
			out.close();
		} catch(Exception e){
			logger.error(e.toString(), e);
		}
	}
	
	/**
	 * 删除流程实例
	 * @param procInsId 流程实例ID
	 * @param reason 删除原因
	 */
	@RequestMapping(value = "/deleteProcIns")
	public String deleteProcIns(String procInsId, String reason) {
		String message = "";
		if (StringUtils.isBlank(reason)){
			message = "请填写删除原因";
		}else{
			runtimeService.deleteProcessInstance(procInsId, reason);
			message = "删除流程实例成功，实例ID=" + procInsId;
		}
		return message;
	}
	
	/* ===============================权限================================== */
	public Map<String, String> getHC(){
		Subject currentUser = SecurityUtils.getSubject();  //shiro管理的session
		Session session = currentUser.getSession();
		return (Map<String, String>)session.getAttribute(Const.SESSION_QX);
	}
	/* ===============================权限================================== */
	
	@InitBinder
	public void initBinder(WebDataBinder binder){
		DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
		binder.registerCustomEditor(Date.class, new CustomDateEditor(format,true));
	}
	
}
